#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Threading.Tasks;
using BitwiseOre.SharedMemory.Core;
using BitwiseOre.SharedMemory.Core.Common;
using NUnit.Framework;
using Moq;

namespace BitwiseOre.Tests.SharedMemory.Connection
{
    [TestFixture]
    public sealed class ReaderFixture : AssertionHelper
    {
        [Test(Description = "Reading an array segment creates a timeout helper.")]
        public void ReadAsync_CreatesTimeoutHelper()
        {
            var created = false;
            Func<TimeSpan, ITimeoutHelper> helper = _ =>
                                                        {
                                                            created = true;
                                                            return new Mock<ITimeoutHelper>().Object;
                                                        };
            var queues = new Mock<IConnectionQueues> {DefaultValue = DefaultValue.Mock};
            queues.Setup(q => q.ReadFromCurrentBuffer(It.IsAny<ArraySegment<byte>>())).Returns(2);

            var reader = new Reader(queues.Object, helper);
            reader.ReadAsync(new ArraySegment<byte>(new byte[] { 1, 2, 3, 4}), TimeSpan.FromMinutes(1));
            
            Expect(created, "No timeout helper was created.");
        }

        [Test(Description = "Reading a byte delegates to the current read buffer.")]
        public void ReadByte_DelegatesToCurrentReadBuffer()
        {
            var queues = new Mock<IConnectionQueues>();

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            reader.ReadByte(TimeSpan.FromMinutes(1));

            queues.Verify(q => q.ReadByteFromCurrentBuffer(),
                          "No attempt was made to read from the current read buffer.");
        }

        [Test(Description = "Reading a byte returns the result of the delgated read from the current " +
                            "read buffer if that result is not the end marker.")]
        public void ReadByte_ReturnsDelegatedResultIfNotEndMarker()
        {
            const char expected = 'a';
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadByteFromCurrentBuffer()).Returns(expected);

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            var result = reader.ReadByte(TimeSpan.FromMinutes(1));

            Expect(result, EqualTo((int)expected), "Unexpected return value when reading a byte.");
        }

        [Test(Description = "Reading a byte delegates to the next read buffer if the delgations to the " +
                            "current one returns an end marker.")]
        public void ReadByte_DelegatesToNextReadBufferIfCurrentBufferReturnsEndMarker()
        {
            const int expected = -1;
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadByteFromCurrentBuffer()).Returns(expected);
            queues.Setup(q => q.ReadFromNextBuffer(It.IsAny<ArraySegment<byte>>(), It.IsAny<ITimeoutHelper>()))
                .Returns(Task.Factory.StartNew(() => 1));

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            reader.ReadByte(TimeSpan.FromMinutes(1));

            queues.Verify(
                q => q.ReadFromNextBuffer(It.Is<ArraySegment<byte>>(a => a.Count == 1),
                                          It.IsAny<ITimeoutHelper>()),
                "Reading a byte did not read from the next buffer.");
        }

        [Test(Description = "Reading a byte returns the byte read from the next buffer if the current buffer " +
                            "returns an end marker.")]
        public void ReadByte_ReturnsReadFromNextBufferIfCurrentBufferReturnsEndMarker()
        {
            const int currentresult = -1;
            const byte nextresult = (byte) 'a';
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadByteFromCurrentBuffer()).Returns(currentresult);
            queues.Setup(q => q.ReadFromNextBuffer(It.IsAny<ArraySegment<byte>>(), It.IsAny<ITimeoutHelper>()))
                .Callback<ArraySegment<byte>, ITimeoutHelper>((a, th) => a.Array[a.Offset] = nextresult)
                .Returns(Task.Factory.StartNew(() => 1));

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            var result = reader.ReadByte(TimeSpan.FromMinutes(1));
            
            Expect(result, EqualTo(nextresult), "Unexpected result returned when reading a byte.");
        }

        [Test(Description = "Reading a byte returns the end marker if both the current buffer and the next " +
                            "buffer return the end markers.")]
        public void ReadByte_ReturnsEndMarkerIfBothCurrentAndNextBuffersReturnEndMarkers()
        {
            const int currentresult = -1;
            const int nextcount = 0;
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadByteFromCurrentBuffer()).Returns(currentresult);
            queues.Setup(q => q.ReadFromNextBuffer(It.IsAny<ArraySegment<byte>>(), It.IsAny<ITimeoutHelper>()))
                .Returns(Task.Factory.StartNew(() => nextcount));

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            var result = reader.ReadByte(TimeSpan.FromMinutes(1));
            
            Expect(result, EqualTo(-1), "End marker was not returned when reading a byte.");
        }

        [Test(Description = "Reading an array segment delegates to the current read buffer.")]
        public void Read_DelegatesToCurrentReadBuffer()
        {
            var array = new ArraySegment<byte>(new byte[5]);
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadFromCurrentBuffer(It.IsAny<ArraySegment<byte>>()))
                .Returns((ArraySegment<byte> a)=> a.Count);

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            reader.Read(array, TimeSpan.FromMinutes(1));

            queues.Verify(q => q.ReadFromCurrentBuffer(It.Is<ArraySegment<byte>>(a => a == array)),
                          "Reading an array did not delegate to the current read buffer.");
        }

        [Test(Description = "Reading an array segment returns the number of bytes read from the current read " +
                            "buffer if not 0.")]
        public void Read_ReturnsBytesReadFromCurrentReadBufferIfNotZero()
        {
            const int expectedcount = 3;
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadFromCurrentBuffer(It.IsAny<ArraySegment<byte>>())).Returns(expectedcount);

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            var actualcount = reader.Read(new ArraySegment<byte>(new byte[5]), TimeSpan.FromMinutes(1));

            Expect(actualcount, EqualTo(expectedcount),
                   "Unepected read count returned when reading an array segment.");
        }

        [Test(Description = "Reading an array segment delgates to the next read buffer if reading from the " +
                            "current readbuffer returns zero.")]
        public void Read_DelegatesToNextReadBufferIfCurrentReadBufferReturnsZero()
        {
            const int currentcount = 0;
            var array = new ArraySegment<byte>(new byte[5]);
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadFromCurrentBuffer(It.IsAny<ArraySegment<byte>>())).Returns(currentcount);
            queues.Setup(q => q.ReadFromNextBuffer(It.IsAny<ArraySegment<byte>>(), It.IsAny<ITimeoutHelper>()))
                .Returns(Task.Factory.StartNew(() => 5));

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            reader.Read(array, TimeSpan.FromMinutes(1));

            queues.Verify(
                q => q.ReadFromNextBuffer(It.Is<ArraySegment<byte>>(a => a == array), It.IsAny<ITimeoutHelper>()),
                "Reading an array segment did not delgate to the next read buffer.");
        }

        [Test(Description = "Reading an array segment returns the number of bytes read from the next " +
                            "buffer if the current read buffer returns 0.")]
        public void Read_ReturnsBytesReadFromNextBufferIfCurrentReadBufferReturnsZero()
        {
            const int currentcount = 0;
            const int nextcount = 5;
            var array = new ArraySegment<byte>(new byte[5]);
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadFromCurrentBuffer(It.IsAny<ArraySegment<byte>>())).Returns(currentcount);
            queues.Setup(q => q.ReadFromNextBuffer(It.IsAny<ArraySegment<byte>>(), It.IsAny<ITimeoutHelper>()))
                .Returns(Task.Factory.StartNew(() => nextcount));

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            var count = reader.Read(array, TimeSpan.FromMinutes(1));
            
            Expect(count, EqualTo(nextcount), "Unexpected count returned when reading an array segment.");
        }

        [Test(Description = "Reading an array segment asynchronously delegates to the current read buffer.")]
        public void ReadAsync_DelegatesToCurrentReadBuffer()
        {
            var array = new ArraySegment<byte>(new byte[5]);
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadFromCurrentBuffer(It.IsAny<ArraySegment<byte>>()))
                .Returns((ArraySegment<byte> a) => a.Count);

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            reader.ReadAsync(array, TimeSpan.FromMinutes(1)).Wait();

            queues.Verify(q => q.ReadFromCurrentBuffer(It.Is<ArraySegment<byte>>(a => a == array)),
                          "Reading an array did not delegate to the current read buffer.");
        }

        [Test(Description = "Reading an array segment asynchronously returns the number of bytes read from " +
                            "the current read buffer if not 0.")]
        public void ReadAsync_ReturnsBytesReadFromCurrentReadBufferIfNotZero()
        {
            const int expectedcount = 3;
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadFromCurrentBuffer(It.IsAny<ArraySegment<byte>>())).Returns(expectedcount);

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            var actualcount = reader.ReadAsync(new ArraySegment<byte>(new byte[5]), TimeSpan.FromMinutes(1));

            Expect(actualcount.Result, EqualTo(expectedcount),
                   "Unepected read count returned when reading an array segment.");
        }

        [Test(Description = "Reading an array segment asynchronously delgates to the next read buffer if " +
                            "reading from the current readbuffer returns zero.")]
        public void ReadAsync_DelegatesToNextReadBufferIfCurrentReadBufferReturnsZero()
        {
            const int currentcount = 0;
            var array = new ArraySegment<byte>(new byte[5]);
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadFromCurrentBuffer(It.IsAny<ArraySegment<byte>>())).Returns(currentcount);
            queues.Setup(q => q.ReadFromNextBuffer(It.IsAny<ArraySegment<byte>>(), It.IsAny<ITimeoutHelper>()))
                .Returns(Task.Factory.StartNew(() => 5));

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            reader.ReadAsync(array, TimeSpan.FromMinutes(1)).Wait();

            queues.Verify(
                q => q.ReadFromNextBuffer(It.Is<ArraySegment<byte>>(a => a == array), It.IsAny<ITimeoutHelper>()),
                "Reading an array segment did not delgate to the next read buffer.");
        }

        [Test(Description = "Reading an array segment asynchronously returns the number of bytes read from " +
                            "the next buffer if the current read buffer returns 0.")]
        public void ReadAsync_ReturnsBytesReadFromNextBufferIfCurrentReadBufferReturnsZero()
        {
            const int currentcount = 0;
            const int nextcount = 5;
            var array = new ArraySegment<byte>(new byte[5]);
            var queues = new Mock<IConnectionQueues>();
            queues.Setup(q => q.ReadFromCurrentBuffer(It.IsAny<ArraySegment<byte>>())).Returns(currentcount);
            queues.Setup(q => q.ReadFromNextBuffer(It.IsAny<ArraySegment<byte>>(), It.IsAny<ITimeoutHelper>()))
                .Returns(Task.Factory.StartNew(() => nextcount));

            var reader = new Reader(queues.Object, _ => new Mock<ITimeoutHelper>().Object);
            var count = reader.ReadAsync(array, TimeSpan.FromMinutes(1));

            Expect(count.Result, EqualTo(nextcount), "Unexpected count returned when reading an array segment.");
        }
    }
}